/*******************************************************************************

Copyright (c) 2003, Robert Cowham and Vaccaperna Systems Ltd.  All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1.  Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.

2.  Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL VACCAPERNA SYSTEMS LTD. BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*******************************************************************************/

/*******************************************************************************
 * Name:	p4ClientUser.cpp
 *
 * Author:	Robert Cowham <robert@vaccaperna.co.uk>
 *
 * Description: 
 *		COM bindings for the Perforce API. User interface class
 * 		for getting Perforce results into COM.
 *
 ******************************************************************************/


#include "stdafx.h"
#include "p4ClientUser.h"

#include "p4interface.h"
#include "TraceUtils.h"
#include "i18napi.h"

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

p4ClientUser::p4ClientUser()
	:	m_BinaryFile(NULL), m_TextFile(NULL), m_spec(NULL), 
		m_specData(NULL), m_TranslateCharset(0), m_tranbuf(NULL),
		m_InfoArray(new StrBufArray()), m_WarningArray(new StrBufArray()),
		m_ErrorArray(new StrBufArray())
{
	try
	{
		STACK_TRACE("p4ClientUser::p4ClientUser")
	}
	catch (...)
	{
		LogException(_T("p4ClientUser::p4ClientUser"));
	}
}

void
p4ClientUser::Reset()
{
	try
	{
		STACK_TRACE("p4ClientUser::Reset")
		// Ensure our arrays are OK.
		m_InfoArray->Clear();
		m_WarningArray->Clear();
		m_ErrorArray->Clear();
		CloseFiles();
	}
	catch (...)
	{
		LogException(_T("p4ClientUser::Reset"));
	}
}

p4ClientUser::~p4ClientUser()
{
	try
	{
		STACK_TRACE("p4ClientUser::~p4ClientUser")
		if (m_spec) delete m_spec;
		if (m_specData) delete m_specData;
		if (m_tranbuf) delete m_tranbuf;
		if (m_InfoArray) delete m_InfoArray;
		if (m_ErrorArray) delete m_ErrorArray;
		if (m_WarningArray) delete m_WarningArray;
		CloseFiles();
	}
	catch (...)
	{
		LogException(_T("p4ClientUser::~p4ClientUser"));
	}
}

// From BSTR to UTF8 string (if appropriate)
LPCSTR p4ClientUser::TranslateFromBSTR(wstring &bs)
{
	USES_CONVERSION;
	if (!TranslateCharset())
	{
		m_buf.Set(W2CA(bs.c_str()));
		return m_buf.Text();
	}
	else
	{
		// Find out how big a buffer we need
		int buflen = WideCharToMultiByte(CP_UTF8, 0, bs.c_str(), bs.length(),
										NULL, 0, NULL, NULL);
		if (m_tranbuf != NULL)
			delete m_tranbuf;
		m_tranbuf = new char[buflen + 1];
		int copied = WideCharToMultiByte(CP_UTF8, 0, bs.data(), bs.length(),
							m_tranbuf, buflen + 1, NULL, NULL);
		if (0 == copied) throw DISP_E_EXCEPTION;
		m_tranbuf[copied] = '\0';   // Make sure NULL terminated as problems otherwise
		return m_tranbuf;
	}
}

LPCSTR p4ClientUser::TranslateFromBSTR(BSTR bs)
{
	USES_CONVERSION;
	if (!TranslateCharset())
	{
		m_buf.Set(W2CA(bs));
		return m_buf.Text();
	}
	else
	{
		// Find out how big a buffer we need
		int buflen = WideCharToMultiByte(CP_UTF8, 0, bs, SysStringLen(bs),
										NULL, 0, NULL, NULL);
		if (m_tranbuf != NULL)
			delete m_tranbuf;
		m_tranbuf = new char[buflen + 1];
		int copied = WideCharToMultiByte(CP_UTF8, 0, bs, SysStringLen(bs),
							m_tranbuf, buflen + 1, NULL, NULL);
		if (0 == copied) throw DISP_E_EXCEPTION;
		m_tranbuf[copied] = '\0';   // Make sure NULL terminated as problems otherwise
		return m_tranbuf;
	}
}

// From UTF8 string to BSTR (if appropriate)
BSTR p4ClientUser::TranslateToBSTR(StrBuf *sb)
{
	USES_CONVERSION;
	CComBSTR result;
	if (!TranslateCharset())
	{
		result = A2CW(sb->Text());
	}
	else
	{
		if (sb->Length())
		{
			// Find out how big a buffer we need
			int buflen = MultiByteToWideChar(CP_UTF8, 0, sb->Text(), 
											sb->Length(), NULL, 0);
			LPWSTR lpWideCharStr = new WCHAR[buflen + 1];
			int copied = MultiByteToWideChar(CP_UTF8, 0, sb->Text(), sb->Length(), 
								lpWideCharStr, buflen + 1);
			if (0 == copied) 
			{
				delete [] lpWideCharStr;
				throw DISP_E_EXCEPTION;
			}
			lpWideCharStr[buflen] = '\0';

			// Now assign to ourselves
			result = lpWideCharStr;
			delete [] lpWideCharStr;
		}
	}
	return result.Detach();
}

void p4ClientUser::TranslateCharset(int newVal)
{
	STACK_TRACE("p4ClientUser::TranslateCharset set")
	m_TranslateCharset = newVal;
}

int p4ClientUser::TranslateCharset()
{
	STACK_TRACE("p4ClientUser::TranslateCharset get")
	return m_TranslateCharset;
}

void p4ClientUser::CloseFiles()
{
	try
	{
		STACK_TRACE("p4ClientUser::CloseFiles")
		Error e;

		if (NULL != m_BinaryFile)
		{
			m_BinaryFile->Close(&e);
			if (e.Test())
			{
				FormatAndOutputError ("Failed to close binary file:", &e);
			}
			delete m_BinaryFile;
			m_BinaryFile = NULL;
		}

		if (NULL != m_TextFile)
		{
			m_TextFile->Close(&e);
			if (e.Test())
			{
				FormatAndOutputError ("Failed to close text file:", &e);
			}
			delete m_TextFile;
			m_TextFile = NULL;
		}
	}
	catch (...)
	{
		LogException(_T("p4ClientUser::Final"));
	}
}


// Prompt user with error message
void p4ClientUser::ErrorPause( char *errBuf, Error *e )
{
	try
	{
		STACK_TRACE("p4ClientUser::ErrorPause")
		OutputError( errBuf );
		MessageBox (NULL, errBuf, _T("p4com"), MB_OK);
	}
	catch (...)
	{
		LogException(_T("p4ClientUser::ErrorPause"));
	}
}



// Writes specified data to the output file.
void p4ClientUser::OutputData(FileSys ** OutFile, StrBuf& FileName, enum FileSysType FType, 
				char *data, int len)
{
	try
	{
		STACK_TRACE("p4ClientUser::OutputData")
		Error e;

		// If we haven't created a temp output file then do so now!
		if (NULL == *OutFile)
		{
			*OutFile = FileSys::CreateGlobalTemp(FType);
			(*OutFile)->ClearDeleteOnClose();

			// Have had some problems with non-unique filenames.
			// Delete the file and ignore any errors.
			(*OutFile)->Unlink(&e);
			e.Clear();

			FileName = (*OutFile)->Name();

			(*OutFile)->Open(FOM_WRITE, &e);

			if (e.Test())
			{
				StrBuf msg;
				msg = "Error opening temp file for write:";
				msg.Append((*OutFile)->Name());
				msg.Append(" - ");
				FormatAndOutputError(msg.Text(), &e);
				return;
			}
		}

		// Now write the data
		(*OutFile)->Write(data, len, &e);
		if (e.Test())
		{
			FormatAndOutputError ("Error writing to temp file :", &e);
			return;
		}
	}
	catch (...)
	{
		LogException(_T("p4ClientUser::OutputData"));
	}
} // OutputData

void p4ClientUser::HandleError(Error *e)
{
	try
	{
		STACK_TRACE("p4ClientUser::HandleError")
		StrBuf m;
		e->Fmt(&m);

		int s = e->GetSeverity();

		// 
		// Empty and informational messages are pushed out as output as nothing
		// worthy of error handling has occurred. Warnings go into the warnings
		// list and the rest are lumped together as errors.
		//

		if (s == E_EMPTY || s == E_INFO)
			AddToArray(m.Text(), m.Length(), m_InfoArray);
		else if (s == E_WARN)
			AddToArray(m.Text(), m.Length(), m_WarningArray);
		else
			AddToArray(m.Text(), m.Length(), m_ErrorArray);
	}
	catch (...)
	{
		LogException(_T("p4ClientUser::HandleError"));
	}

} // HandleError


// Just store the information for later use
void p4ClientUser::OutputInfo(char level, char *data)
{
	try
	{
		STACK_TRACE("p4ClientUser::OutputInfo")
		AddToArray(data, strlen(data), m_InfoArray);
	}
	catch (...)
	{
		LogException(_T("p4ClientUser::OutputInfo"));
	}
}

// Just store the information for later use
void p4ClientUser::OutputWarning(char *data)
{
	try
	{
		STACK_TRACE("p4ClientUser::OutputWarning")
		AddToArray(data, strlen(data), m_WarningArray);
	}
	catch (...)
	{
		LogException(_T("p4ClientUser::OutputWarning"));
	}
}

// Just store the information for later use
void p4ClientUser::OutputError(char *errBuf)
{
	try
	{
		STACK_TRACE("p4ClientUser::OutputError")
		AddToArray(errBuf, strlen(errBuf), m_ErrorArray);
	}
	catch (...)
	{
		LogException(_T("p4ClientUser::OutputError"));
	}
}

// Write the data to a temp file
void p4ClientUser::OutputText(char *data, int len)
{
	try
	{
		STACK_TRACE("p4ClientUser::OutputText")
		OutputData(&m_TextFile, m_TextFileName, FST_TEXT, data, len);
	}
	catch (...)
	{
		LogException(_T("p4ClientUser::OutputText"));
	}
}

// Write the data to a temp file
void p4ClientUser::OutputBinary(char *data, int len)
{
	try
	{
		STACK_TRACE("p4ClientUser::OutputBinary")
		OutputData(&m_BinaryFile, m_BinaryFileName, FST_BINARY, data, len);
	}
	catch (...)
	{
		LogException(_T("p4ClientUser::OutputBinary"));
	}
}

void p4ClientUser::Prompt( const StrPtr &msg, StrBuf &rsp, 
				int noEcho, Error *e )
{
	try
	{
		STACK_TRACE("p4ClientUser::Prompt")
		rsp.Set(m_input);
	}
	catch (...)
	{
		LogException(_T("p4ClientUser::Prompt"));
	}
}

/*
 * Diff support for API. Function basically swiped from p4Ruby implementation.
 * Since the Diff class only writes its output
 * to files, we run the requested diff putting the output into a temporary
 * file. Then we read the file in and add its contents line by line to the 
 * results.
 */

void p4ClientUser::Diff( FileSys *f1, FileSys *f2, int doPage, 
                char *diffFlags, Error *e )
{
	try
	{
		STACK_TRACE("p4ClientUser::Diff")

		//
		// Duck binary files. Much the same as ClientUser::Diff, we just
		// put the output into result space rather than stdout.
		//
		if( !f1->IsTextual() || !f2->IsTextual() )
		{
			if ( f1->Compare( f2, e ) )
			{
				char *data = "(... files differ ...)";
				AddToArray(data, strlen(data), m_InfoArray);
			}
			return;
		}

		// Time to diff the two text files. Need to ensure that the
		// files are in binary mode, so we have to create new FileSys
		// objects to do this.

		FileSys *f1_bin = FileSys::Create( FST_BINARY );
		FileSys *f2_bin = FileSys::Create( FST_BINARY );
		FileSys *t = FileSys::CreateGlobalTemp( f1->GetType() );

		f1_bin->Set( f1->Name() );
		f2_bin->Set( f2->Name() );

		{
			//
			// In its own block to make sure that the diff object is deleted
			// before we delete the FileSys objects.
			//
			::
			Diff d;

			d.SetInput( f1_bin, f2_bin, diffFlags, e );
			if ( ! e->Test() ) d.SetOutput( t->Name(), e );
			if ( ! e->Test() ) d.DiffWithFlags( diffFlags );
			d.CloseOutput( e );

			// OK, now we have the diff output, read it in and add it to 
			// the output.
			if ( ! e->Test() ) t->Open( FOM_READ, e );
			if ( ! e->Test() ) 
			{
				StrBuf  b;
				while( t->ReadLine( &b, e ) )
				{
					AddToArray(b.Text(), b.Length(), m_InfoArray);
				}
			}
		}

		delete t;
		delete f1_bin;
		delete f2_bin;

		if ( e->Test() ) HandleError( e );
	}
	catch (...)
	{
		LogException(_T("p4ClientUser::OutputText"));
	}
}

// Add specified text to specified array
void
p4ClientUser::AddToArray(char *data, int len, StrBufArray *sba)
{
	STACK_TRACE("p4ClientUser::AddToArray")

	sba->Put(data);
}; // AddToArray

void
p4ClientUser::OutputStat(StrDict *results)
{
	try
	{
		STACK_TRACE("p4ClientUser::OutputStat")
		StrPtr *data = results->GetVar("data");
		StrPtr *spec = results->GetVar("specdef");

		if (spec && data)
		{
			Error e;
			if (m_spec) delete m_spec;
			if (m_specData) delete m_specData;
			m_spec = new Spec(spec->Text(), "");
			m_specData = new SpecDataTable();
			m_spec->Parse(data->Text(), m_specData, &e);
			if(e.Test())
			{
				HandleError(&e);
				return;
			}
		}
		else
		{
		   int i;
		   StrBuf msg;
		   StrRef var, val;

		   // Dump out the variables, using the GetVar( x ) interface.
		   // Taken from API doc.
		   for( i = 0; results->GetVar( i, var, val ); i++ )
		   {
			  if( var == "func" ) continue;

			  // otherAction and otherOpen go at level 2, as per 99.1 + earlier
			  msg.Clear();
			  msg << var << " " << val;
			  char level = strncmp( var.Text(), "other", 5 ) ? '1' : '2';
			  OutputInfo( level, msg.Text() );
		   }
		}
	}
	catch (...)
	{
		LogException(_T("p4ClientUser::OutputStat"));
	}

}


void
p4ClientUser::InputData( StrBuf *strbuf, Error *e )
{
	try
	{
		STACK_TRACE("p4ClientUser::InputData")
		StrBuf specText;
		m_spec->Format(m_specData, &specText);
		strbuf->Set(specText.Text());
	}
	catch (...)
	{
		LogException(_T("p4ClientUser::InputData"));
	}
}

BSTR p4ClientUser::GetVar(LPCSTR var)
{
	try
	{
		STACK_TRACE("p4ClientUser::GetVar get")
		CComBSTR value;

		if (m_specData)
		{
			StrPtr *sp = m_specData->Dict()->GetVar(var);
			if (sp != NULL)
			{
				StrBuf sb;
				sb.Set(sp->Text());
				value = TranslateToBSTR(&sb);
			}
		}
		return value.Detach();
	}
	catch (...)
	{
		LogException(_T("p4ClientUser::GetVar"));
		return NULL;
	}
}

int p4ClientUser::VarExists(LPCSTR var)
{
	try
	{
		STACK_TRACE("p4ClientUser::VarExists")

		if (m_specData)
		{
			StrPtr *sp = m_specData->Dict()->GetVar(var);
			if (sp != NULL)
			{
				return 1;
			}
		}
		return 0;
	}
	catch (...)
	{
		LogException(_T("p4ClientUser::VarExists"));
		return NULL;
	}
}

void p4ClientUser::SetVar(LPCSTR varName, BSTR newVal)
{
	try
	{
		STACK_TRACE("p4ClientUser::SetVar")
		if (m_specData)
		{
			m_specData->Dict()->ReplaceVar(varName, TranslateFromBSTR(newVal));
		}
	}
	catch (...)
	{
		LogException(_T("p4ClientUser::SetVar"));
	}
}

void p4ClientUser::SetArrayVar(LPCSTR varName, StrBufArray *newVal)
{
	int     i;
	char    idx[32];
    char *  var;
	StrBuf	s;

	STACK_TRACE("p4ClientUser::SetArrayVar")
	if (!m_specData)
		return;
    var = (char *)idx;
	char * tag = (char *)varName;

	// Remove all files in the spec list and replace with new set	
	for (i = 0; ; i++)
	{
        wsprintfA(idx, "%s%d", tag, i);
        if (!m_specData->Dict()->GetVar(var))
			break;
        m_specData->Dict()->RemoveVar(var);
	}

	for (i = 0; i < newVal->Count(); i++)
	{
        wsprintfA(idx, "%s%d", tag, i);
		s = (LPCSTR)newVal->Get(i);
		m_specData->Dict()->SetVar(idx, s);
	}

}

StrBufArray *p4ClientUser::GetArrayVar(LPCSTR varName)
{
    StrBufArray *sba = new StrBufArray();
    int     i;
    char    idx[32];
    char *  var;
    StrPtr  *field;

	STACK_TRACE("p4ClientUser::GetArrayVar")
	if (m_specData)
	{
		var = (char *) idx;
		char *tag = (char *)varName;
		for ( i = 0; ; i++ )
		{
			wsprintfA( idx, "%s%d", tag, i );
			if ( field = m_specData->Dict()->GetVar( var ) )
			{
				sba->Put(field->Text());
			}
			else
				break;
		}
	}

    return sba;
}
